home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Tools / Languages / Icon 8.1 / mep1 / Samples / Programs / rsg.icn < prev    next >
Encoding:
Text File  |  1989-05-09  |  9.4 KB  |  366 lines  |  [TEXT/PICN]

  1. ############################################################################
  2. #
  3. #  rsg.icn
  4. #  
  5. #     This program generates randomly selected strings ("sen-
  6. #  tences") from a grammar specified by the user.  Grammars are
  7. #  basically context-free and resemble BNF in form, although there
  8. #  are a number of extensions.
  9. #  
  10. #     The program works interactively, allowing the user to build,
  11. #  test, modify, and save grammars. Input to rsg consists of various
  12. #  kinds of specifications, which can be intermixed:
  13. #  
  14. #     Productions define nonterminal symbols in a syntax similar to
  15. #  the rewriting rules of BNF with various alternatives consisting
  16. #  of the concatenation of nonterminal and terminal symbols.  Gen-
  17. #  eration specifications cause the generation of a specified number
  18. #  of sentences from the language defined by a given nonterminal
  19. #  symbol.  Grammar output specifications cause the definition of a
  20. #  specified nonterminal or the entire current grammar to be written
  21. #  to a given file.  Source specifications cause subsequent input to
  22. #  be read from a specified file.
  23. #  
  24. #     In addition, any line beginning with # is considered to be a
  25. #  comment, while any line beginning with = causes the rest of that
  26. #  line to be used subsequently as a prompt to the user whenever rsg
  27. #  is ready for input (there normally is no prompt). A line consist-
  28. #  ing of a single = stops prompting.
  29. #  
  30. #  Productions: Examples of productions are:
  31. #  
  32. #          <expr>::=<term>|<term>+<expr>
  33. #          <term>::=<elem>|<elem>*<term>
  34. #          <elem>::=x|y|z|(<expr>)
  35. #  
  36. #  Productions may occur in any order. The definition for a nonter-
  37. #  minal symbol can be changed by specifying a new production for
  38. #  it.
  39. #  
  40. #     There are a number of special devices to facilitate the defin-
  41. #  ition of grammars, including eight predefined, built-in nontermi-
  42. #  nal symbols:
  43. #
  44. #     symbol   definition
  45. #     <lb>     <
  46. #     <rb>     >
  47. #     <vb>     |
  48. #     <nl>     newline
  49. #     <>       empty string
  50. #     <&lcase> any single lowercase letter
  51. #     <&ucase> any single uppercase letter
  52. #     <&digit> any single digit
  53. #  
  54. #  In addition, if the string between a < and a > begins and ends
  55. #  with a single quotation mark, it stands for any single character
  56. #  between the quotation marks. For example,
  57. #  
  58. #          <'xyz'>
  59. #  
  60. #  is equivalent to
  61. #  
  62. #          x|y|z
  63. #  
  64. #  Generation Specifications: A generation specification consists of
  65. #  a nonterminal symbol followed by a nonnegative integer. An exam-
  66. #  ple is
  67. #  
  68. #          <expr>10
  69. #  
  70. #  which specifies the generation of 10 <expr>s. If the integer is
  71. #  omitted, it is assumed to be 1. Generated sentences are written
  72. #  to standard output.
  73. #  
  74. #  Grammar Output Specifications: A grammar output specification
  75. #  consists of a nonterminal symbol, followed by ->, followed by a
  76. #  file name. Such a specification causes the current definition of
  77. #  the nonterminal symbol to be written to the given file. If the
  78. #  file is omitted, standard output is assumed. If the nonterminal
  79. #  symbol is omitted, the entire grammar is written out. Thus,
  80. #  
  81. #          ->
  82. #  
  83. #  causes the entire grammar to be written to standard output.
  84. #  
  85. #  Source Specifications: A source specification consists of @ fol-
  86. #  lowed by a file name.  Subsequent input is read from that file.
  87. #  When an end of file is encountered, input reverts to the previous
  88. #  file. Input files can be nested.
  89. #  
  90. #  Options: The following parameter string options are available:
  91. #  
  92. #       -s n Set the seed for random generation to n.  The default
  93. #            seed is 0.
  94. #  
  95. #       -l n Terminate generation if the number of symbols remaining
  96. #            to be processed exceeds n. The default is limit is 1000.
  97. #  
  98. #       -t   Trace the generation of sentences. Trace output goes to
  99. #            standard error output.
  100. #  
  101. #  Diagnostics: Syntactically erroneous input lines are noted but
  102. #  are otherwise ignored.  Specifications for a file that cannot be
  103. #  opened are noted and treated as erroneous.
  104. #  
  105. #     If an undefined nonterminal symbol is encountered during gen-
  106. #  eration, an error message that identifies the undefined symbol is
  107. #  produced, followed by the partial sentence generated to that
  108. #  point. Exceeding the limit of symbols remaining to be generated
  109. #  as specified by the -l option is handled similarly.
  110. #  
  111. #  Caveats: Generation may fail to terminate because of a loop in
  112. #  the rewriting rules or, more seriously, because of the progres-
  113. #  sive accumulation of nonterminal symbols. The latter problem can
  114. #  be identified by using the -t option and controlled by using the
  115. #  -l option. The problem often can be circumvented by duplicating
  116. #  alternatives that lead to fewer rather than more nonterminal sym-
  117. #  bols. For example, changing
  118. #  
  119. #          <term>::=<elem>|<elem>*<term>
  120. #  
  121. #  to
  122. #  
  123. #          <term>::=<elem>|<elem>|<elem>*<term>
  124. #  
  125. #  increases the probability of selecting <elem> from 1/2 to 2/3.
  126. #  
  127. #     There are many possible extensions to the program. One of the
  128. #  most useful would be a way to specify the probability of select-
  129. #  ing an alternative.
  130. #
  131. ############################################################################  
  132. #
  133. #  Links: getopt
  134. #
  135. ############################################################################
  136. link getopt
  137.  
  138. global defs, filelist, in, limit, prompt, tswitch
  139.  
  140. record nonterm(name)
  141. record charset(chars)
  142.  
  143. procedure main(args)
  144.     local line, plist, s, opts
  145.                                     # procedures to try on input lines
  146.     plist := [define,generate,grammar,source,comment,prompter,error]
  147.     defs := table()                    # table of definitions
  148.     defs["lb"] := [["<"]]            # built-in definitions
  149.     defs["rb"] := [[">"]]
  150.     defs["vb"] := [["|"]]
  151.     defs["nl"] := [["\n"]]
  152.     defs[""] := [[""]]
  153.     defs["&lcase"] := [[charset(&lcase)]]
  154.     defs["&ucase"] := [[charset(&ucase)]]
  155.     defs["&digit"] := [[charset(&digits)]]
  156.  
  157.     opts := getopt(args,"tl+s+")[1]
  158.     limit := \opts["l"] | 1000
  159.     tswitch := \opts["t"]
  160.     &random := \opts["s"]
  161.         
  162.     filelist := [&input]            # stack of input files
  163.     prompt := ""
  164.     while in := pop(filelist) do {    # process all files
  165.         repeat {
  166.             line := read(in) | break
  167.             while line[-1] == "\\" do line := line[1:-1] || read(in) | break
  168.             (!plist)(line)
  169.             }
  170.         close(in)
  171.         }
  172. end
  173.  
  174. #  process alternatives
  175. #
  176. procedure alts(defn)
  177.     local alist
  178.     alist := []
  179.     defn ? while put(alist,syms(tab(upto('|') | 0))) do move(1) | break
  180.     return alist
  181. end
  182.  
  183. #  look for comment
  184. #
  185. procedure comment(line)
  186.     if line[1] == "#" then return
  187. end
  188.  
  189. #  look for definition
  190. #
  191. procedure define(line)
  192.     return line ?
  193.         defs[(="<",tab(find(">::=")))] := (move(4),alts(tab(0)))
  194. end
  195.  
  196. #  define nonterminal
  197. #
  198. procedure defnon(sym)
  199.     local chars, name
  200.     if sym ? {
  201.         ="'" &
  202.         chars := cset(tab(-1)) &
  203.         ="'"
  204.         }
  205.     then return charset(chars)
  206.     else return nonterm(sym)
  207. end
  208.  
  209. #  note erroneous input line
  210. #
  211. procedure error(line)
  212.     write("*** erroneous line:  ",line)
  213.     return
  214. end
  215.  
  216. #  generate sentences
  217. #
  218. procedure gener(goal)
  219.     local pending, symbol
  220.     pending := [nonterm(goal)]
  221.     while symbol := get(pending) do {
  222.         if \tswitch then
  223.             write(&errout,symimage(symbol),listimage(pending))
  224.         case type(symbol) of {
  225.             "string":   writes(symbol)
  226.             "charset":  writes(?symbol.chars)
  227.             "nonterm":  {
  228.                 pending := ?\defs[symbol.name] ||| pending | {
  229.                     write(&errout,"*** undefined nonterminal:  <",symbol.name,">")
  230.                     break 
  231.                     }
  232.                 if *pending > \limit then {
  233.                     write(&errout,"*** excessive symbols remaining")
  234.                     break 
  235.                     }
  236.                 }
  237.             }
  238.         }
  239.     write()
  240. end
  241.  
  242. #  look for generation specification
  243. #
  244. procedure generate(line)
  245.     local goal, count
  246.     if line ? {
  247.         ="<" &
  248.         goal := tab(upto('>')) \ 1 &
  249.         move(1) &
  250.         count := (pos(0) & 1) | integer(tab(0))
  251.         }
  252.     then {
  253.         every 1 to count do
  254.             gener(goal)
  255.         return
  256.         }
  257.     else fail
  258. end
  259.  
  260. #  get right hand side of production
  261. #
  262. procedure getrhs(a)
  263.     local rhs
  264.     rhs := ""
  265.     every rhs ||:= listimage(!a) || "|"
  266.     return rhs[1:-1]
  267. end
  268.  
  269. #  look for request to write out grammar
  270. #
  271. procedure grammar(line)
  272.     local file, out, name
  273.     if line ? {
  274.         name := tab(find("->")) &
  275.         move(2) &
  276.         file := tab(0) &
  277.         out := if *file = 0 then &output else {
  278.             open(file,"w") | {
  279.                 write(&errout,"*** cannot open ",file)
  280.                 fail
  281.                 }
  282.             }
  283.         }
  284.     then {
  285.         (*name = 0) | (name[1] == "<" & name[-1] == ">") | fail
  286.         pwrite(name,out)
  287.         if *file ~= 0 then close(out)
  288.         return
  289.         }
  290.     else fail
  291. end
  292.  
  293. #  produce image of list of grammar symbols
  294. #
  295. procedure listimage(a)
  296.     local s, x
  297.     s := ""
  298.     every x := !a do
  299.         s ||:= symimage(x)
  300.     return s
  301. end
  302.  
  303. #  look for new prompt symbol
  304. #
  305. procedure prompter(line)
  306.     if line[1] == "=" then {
  307.         prompt := line[2:0]
  308.         return
  309.         }
  310. end
  311.  
  312. #  write out grammar
  313. #
  314. procedure pwrite(name,ofile)
  315.     local nt, a
  316.     static builtin
  317.     initial builtin := ["lb","rb","vb","nl","","&lcase","&ucase","&digit"]
  318.     if *name = 0 then {
  319.         a := sort(defs,3)
  320.         while nt := get(a) do {
  321.             if nt == !builtin then {
  322.                 get(a)
  323.                 next
  324.                 }
  325.             write(ofile,"<",nt,">::=",getrhs(get(a)))
  326.             }
  327.         }
  328.     else write(ofile,name,"::=",getrhs(\defs[name[2:-1]])) |
  329.         write("*** undefined nonterminal:  ",name)
  330. end
  331.  
  332. #  look for file with input
  333. #
  334. procedure source(line)
  335.     local new
  336.     return line ? {
  337.         ="@" &
  338.         (new := open(tab(0))) &
  339.         push(filelist,in) &
  340.         (in := new)
  341.         }
  342.         
  343. end
  344.  
  345. #  produce string image of grammar symbol
  346. #
  347. procedure symimage(x)
  348.     return case type(x) of {
  349.         "string":   x
  350.         "nonterm":  "<" || x.name || ">"
  351.         "charset":  "<'" || x.chars || "'>"
  352.         }
  353. end
  354.  
  355. #  process the symbols in an alternative
  356. #
  357. procedure syms(alt)
  358.     local slist
  359.     static nonbrack
  360.     initial nonbrack := ~'<'
  361.     slist := []
  362.     alt ? while put(slist,tab(many(nonbrack)) |
  363.         defnon(2(="<",tab(upto('>')),move(1))))
  364.     return slist
  365. end
  366.